package com.anyext.storage.mapping.impl;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import com.anyext.storage.exception.MappingException;
import com.anyext.storage.mapping.BeanProxy;
import com.anyext.storage.mapping.BeanWrapper;

public class BeanWrapperImpl implements BeanWrapper {

	private Map<String, PropertyDescriptor>	propertyDescriptors	= new HashMap<String, PropertyDescriptor>();
	private Class<?>						beanClass;

	public BeanWrapperImpl(Class<?> beanClass) {
		this.beanClass = beanClass;
		BeanInfo bi;
		try {
			bi = Introspector.getBeanInfo(beanClass);
		} catch (IntrospectionException e) {
			throw new MappingException("fail to contruct beanwrapper: " + beanClass, e);
		}
		PropertyDescriptor[] pd = bi.getPropertyDescriptors();
		// FIXME: cann't handle property:UpperFirstChart
		for (int i = 0; i < pd.length; i++) {
			this.propertyDescriptors.put(pd[i].getName(), pd[i]);
		}
	}

	@Override
	public Class<?> getBeanClass() {
		return beanClass;
	}

	@Override
	public Map<String, PropertyDescriptor> getPropertyDescriptors() {
		return this.propertyDescriptors;
	}

	@Override
	public Object getValue(Object beanInstance, String propertyName) {
		PropertyDescriptor pd = this.propertyDescriptors.get(propertyName);
		if (pd == null) {
			throw new MappingException("unknown property[" + propertyName + "] in: " + this.beanClass);
		}
		Method readMethod = pd.getReadMethod();
		if (readMethod != null) {
			try {
				return readMethod.invoke(beanInstance);
			} catch (Exception e) {
				throw new MappingException("property:" + propertyName + " not readable in :" + this.beanClass, e);
			}
		} else {
			throw new MappingException("property:" + propertyName + " not readable in :" + this.beanClass);
		}
	}

	@Override
	public Object getValueUnderProxy(Object beanInstance, String propertyName) {
		if (!(beanInstance instanceof BeanProxy)) {
			return this.getValue(beanInstance, propertyName);
		} else {
			PropertyDescriptor pd = this.propertyDescriptors.get(propertyName);
			if (pd == null) {
				throw new MappingException("unknown property[" + propertyName + "] in: " + this.beanClass);
			}
			Method readMethod = pd.getReadMethod();
			if (readMethod != null) {
				try {
					return ((BeanProxy) beanInstance).invokeUnderProxy(readMethod);
				} catch (Exception e) {
					throw new MappingException("property:" + propertyName + " not readable in :" + this.beanClass, e);
				}
			} else {
				throw new MappingException("property:" + propertyName + " not readable in :" + this.beanClass);
			}
		}
	}

	@Override
	public void setValue(Object beanInstance, String propertyName, Object... value) {
		PropertyDescriptor pd = this.propertyDescriptors.get(propertyName);
		if (pd == null) {
			throw new MappingException("unknown property[" + propertyName + "] in: " + this.beanClass);
		}
		Method writeMethod = pd.getWriteMethod();
		if (writeMethod != null) {
			try {
				writeMethod.invoke(beanInstance, value);
			} catch (Exception e) {
				throw new MappingException("property:" + propertyName + " not writable in :" + this.beanClass, e);
			}
		} else {
			throw new MappingException("property:" + propertyName + " not writable in :" + this.beanClass);
		}
	}

	@Override
	public void setValueUnderProxy(Object beanInstance, String propertyName, Object... value) {
		if (!(beanInstance instanceof BeanProxy)) {
			this.setValue(beanInstance, propertyName, value);
		} else {
			PropertyDescriptor pd = this.propertyDescriptors.get(propertyName);
			if (pd == null) {
				throw new MappingException("unknown property[" + propertyName + "] in: " + this.beanClass);
			}
			Method writeMethod = pd.getWriteMethod();
			if (writeMethod != null) {
				try {
					((BeanProxy) beanInstance).invokeUnderProxy(writeMethod, value);
				} catch (Exception e) {
					throw new MappingException("property:" + propertyName + " not writable in :" + this.beanClass, e);
				}
			} else {
				throw new MappingException("property:" + propertyName + " not writable in :" + this.beanClass);
			}
		}
	}

	@Override
	public boolean hasReadMethod(String propertyName) {
		PropertyDescriptor pd = this.propertyDescriptors.get(propertyName);
		return pd != null && pd.getReadMethod() != null;
	}

	@Override
	public Method getReadMethod(String propertyName) {
		PropertyDescriptor pd = this.propertyDescriptors.get(propertyName);
		if (pd == null) {
			throw new MappingException("unknown property[" + propertyName + "] in: " + this.beanClass);
		}
		Method readMethod = pd.getReadMethod();
		if (readMethod != null) {
			throw new MappingException("read method not found for property[" + propertyName + "] in: " + this.beanClass);
		}
		return readMethod;
	}

	@Override
	public boolean hasWriteMethod(String propertyName) {
		PropertyDescriptor pd = this.propertyDescriptors.get(propertyName);
		return pd != null && pd.getWriteMethod() != null;
	}

	@Override
	public Method getWriteMethod(String propertyName) {
		PropertyDescriptor pd = this.propertyDescriptors.get(propertyName);
		if (pd == null) {
			throw new MappingException("unknown property[" + propertyName + "] in: " + this.beanClass);
		}
		Method writeMethod = pd.getWriteMethod();
		if (writeMethod != null) {
			throw new MappingException("write method not found for property[" + propertyName + "] in: " + this.beanClass);
		}
		return writeMethod;
	}

	@Override
	public boolean hasProperty(String propertyName) {
		PropertyDescriptor pd = this.propertyDescriptors.get(propertyName);
		return pd != null;
	}

	@Override
	public Class<?> getPropertyType(String propertyName) {
		PropertyDescriptor pd = this.propertyDescriptors.get(propertyName);
		if (pd == null) {
			throw new MappingException("unknown property[" + propertyName + "] in: " + this.beanClass);
		}
		return pd.getPropertyType();
	}

}
